﻿using System;

namespace Silver3D.Math
{
    public struct Matrix33
    {
        public static Matrix33 Zero { get { return new Matrix33(); } }
        public static Matrix33 Identity {  get { return new Matrix33(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0); } }

        private double m11, m12, m13, m21, m22, m23, m31, m32, m33;

        public double M11 { get { return m11; } set { m11 = value; } }
        public double M12 { get { return m12; } set { m12 = value; } }
        public double M13 { get { return m13; } set { m13 = value; } }

        public double M21 { get { return m21; } set { m21 = value; } }
        public double M22 { get { return m22; } set { m22 = value; } }
        public double M23 { get { return m23; } set { m23 = value; } }

        public double M31 { get { return m31; } set { m31 = value; } }
        public double M32 { get { return m32; } set { m32 = value; } }
        public double M33 { get { return m33; } set { m33 = value; } }

        public Matrix33 Transpose
        {
            get { return new Matrix33(m11, m21, m31, m12, m22, m32, m13, m23, m33); }
        }

        public double Determinant
        {
            get
            {
                return (m11 * m22 * m33) + (m12 * m23 * m31) + (m13 * m21 * m32)
                    - (m11 * m23 * m32) - (m12 * m21 * m33) - (m13 * m22 * m31);
            }
        }

        public bool IsInvertible
        {
            get { return !Util.IsEquivalent(0.0, Determinant); }
        }

        public Matrix33 Inverse
        {
            get
            {
                // optimize this as needed

                double det = Determinant;
                if (det == 0.0)
                    throw new InvalidOperationException("Cannot inverse a singular matrix.");

                return (1.0 / det) * new Matrix33(
                    (m22 * m33 - m23 * m32), -(m21 * m33 - m23 * m31), (m21 * m32 - m22 * m31),
                    -(m12 * m33 - m13 * m32), (m11 * m33 - m13 * m31), -(m11 * m32 - m12 * m31),
                    (m12 * m23 - m13 * m22), -(m11 * m23 - m13 * m21), (m11 * m22 - m12 * m21)).Transpose;
            }
        }

        public bool IsIdentity
        {
            get { return this == Matrix33.Identity; }
        }

        public Matrix33(double m11, double m12, double m13, double m21, double m22, double m23, double m31, double m32, double m33)
        {
            this.m11 = m11;
            this.m12 = m12;
            this.m13 = m13;

            this.m21 = m21;
            this.m22 = m22;
            this.m23 = m23;

            this.m31 = m31;
            this.m32 = m32;
            this.m33 = m33;
        }

        public Matrix33(Vec3 row1, Vec3 row2, Vec3 row3)
            : this(row1.X, row1.Y, row1.Z, row2.X, row2.Y, row2.Z, row3.X, row3.Y, row3.Z)
        {
        }

        public Matrix33(Matrix33 copy)
        {
            this.m11 = copy.m11;
            this.m12 = copy.m12;
            this.m13 = copy.m13;

            this.m21 = copy.m21;
            this.m22 = copy.m22;
            this.m23 = copy.m23;

            this.m31 = copy.m31;
            this.m32 = copy.m32;
            this.m33 = copy.m33;
        }

        public static Matrix33 operator -(Matrix33 m)
        {
            return new Matrix33(-m.m11, -m.m12, -m.m13, -m.m21, -m.m22, -m.m23, -m.m31, -m.m32, -m.m33);
        }

        public static Matrix33 operator -(Matrix33 a, Matrix33 b)
        {
            return new Matrix33(
                a.m11 - b.m11, a.m12 - b.m12, a.m13 - b.m13,
                a.m21 - b.m21, a.m22 - b.m22, a.m23 - b.m23,
                a.m31 - b.m31, a.m32 - b.m32, a.m33 - b.m33);
        }

        public static Matrix33 operator +(Matrix33 a, Matrix33 b)
        {
            return new Matrix33(
                a.m11 + b.m11, a.m12 + b.m12, a.m13 + b.m13,
                a.m21 + b.m21, a.m22 + b.m22, a.m23 + b.m23,
                a.m31 + b.m31, a.m32 + b.m32, a.m33 + b.m33);
        }

        public static Matrix33 operator *(Matrix33 a, double s)
        {
            return new Matrix33(
                a.m11 * s, a.m12 * s, a.m13 * s,
                a.m21 * s, a.m22 * s, a.m23 * s,
                a.m31 * s, a.m32 * s, a.m33 * s);
        }

        public static Matrix33 operator *(double s, Matrix33 a)
        {
            return a * s;
        }

        public static Matrix33 operator /(Matrix33 a, double s)
        {
            double sInv = 1.0 / s;
            return new Matrix33(
                a.m11 * sInv, a.m12 * sInv, a.m13 * sInv,
                a.m21 * sInv, a.m22 * sInv, a.m23 * sInv,
                a.m31 * sInv, a.m32 * sInv, a.m33 * sInv);
        }

        public static Matrix33 operator /(double s, Matrix33 a)
        {
            return a / s;
        }

        public static Matrix33 operator *(Matrix33 a, Matrix33 b)
        {
            return new Matrix33(
               a.m11 * b.m11 + a.m12 * b.m21 + a.m13 * b.m31,
               a.m11 * b.m12 + a.m12 * b.m22 + a.m13 * b.m32,
               a.m11 * b.m13 + a.m12 * b.m23 + a.m13 * b.m33,

               a.m21 * b.m11 + a.m22 * b.m21 + a.m23 * b.m31,
               a.m21 * b.m12 + a.m22 * b.m22 + a.m23 * b.m32,
               a.m21 * b.m13 + a.m22 * b.m23 + a.m23 * b.m33,

               a.m31 * b.m11 + a.m32 * b.m21 + a.m33 * b.m31,
               a.m31 * b.m12 + a.m32 * b.m22 + a.m33 * b.m32,
               a.m31 * b.m13 + a.m32 * b.m23 + a.m33 * b.m33);
        }

        public static Vec3 operator *(Matrix33 a, Vec3 b)
        {
            return new Vec3(
                a.m11 * b.X + a.m12 * b.Y + a.m13 * b.Z,
                a.m21 * b.X + a.m22 * b.Y + a.m23 * b.Z,
                a.m31 * b.X + a.m32 * b.Y + a.m33 * b.Z);
        }

        public static bool operator ==(Matrix33 a, Matrix33 b)
        {
            return
                a.m11 == b.m11 &&
                a.m12 == b.m12 &&
                a.m13 == b.m13 &&

                a.m21 == b.m21 &&
                a.m22 == b.m22 &&
                a.m23 == b.m23 &&

                a.m31 == b.m31 &&
                a.m32 == b.m32 &&
                a.m33 == b.m33;
        }

        public static bool operator !=(Matrix33 a, Matrix33 b)
        {
            return !(a == b);
        }

        public Vec2 TransformPoint(Vec2 p)
        {
            Vec3 v = this * (new Vec3(p.X, p.Y, 1.0));
            return v.ToVec2();
        }

        public Vec2 TransformVector(Vec2 v)
        {
            Vec3 vec = this * (new Vec3(v.X, v.Y, 0.0));
            return new Vec2(vec);
        }

        public void Invert()
        {
            this = Inverse;
        }

        public override string ToString()
        {
            return string.Format("Matrix33(({0},{1},{2}),({3},{4},{5}),({6},{7},{8}))",
                m11, m12, m13, m21, m22, m23, m31, m32, m33);
        }

        public bool IsEquivalent(Matrix33 m)
        {
            if (m == null) return false;
            return
                Util.IsEquivalent(m11, m.m11) && Util.IsEquivalent(m12, m.m12) && Util.IsEquivalent(m13, m.m13) &&
                Util.IsEquivalent(m21, m.m21) && Util.IsEquivalent(m22, m.m22) && Util.IsEquivalent(m23, m.m23) &&
                Util.IsEquivalent(m31, m.m31) && Util.IsEquivalent(m32, m.m32) && Util.IsEquivalent(m33, m.m33);
        }

        public override bool Equals(object obj)
        {
            return (obj != null) && (obj is Matrix33) && ((Matrix33)obj) == this;
        }
        
        public override int GetHashCode()
        {
            return
                m11.GetHashCode() ^ m12.GetHashCode() ^ m13.GetHashCode() ^
                m21.GetHashCode() ^ m22.GetHashCode() ^ m23.GetHashCode() ^
                m31.GetHashCode() ^ m32.GetHashCode() ^ m33.GetHashCode();
        }

        public static Matrix33 CreateRotationX(double radians)
        {
            double cos = System.Math.Cos(radians);
            double sin = System.Math.Sin(radians);
            return new Matrix33(
                1.0, 0.0, 0.0,
                0.0, cos, sin,
                0.0, -sin, cos);
        }

        public static Matrix33 CreateRotationY(double radians)
        {
            double cos = System.Math.Cos(radians);
            double sin = System.Math.Sin(radians);
            return new Matrix33(
                cos, 0.0, -sin,
                0.0, 1.0, 0.0,
                sin, 0.0, cos);
        }

        public static Matrix33 CreateRotationZ(double radians)
        {
            double cos = System.Math.Cos(radians);
            double sin = System.Math.Sin(radians);
            return new Matrix33(
                cos, sin, 0.0,
                -sin, cos, 0.0,
                0.0, 0.0, 1.0);
        }

        public static Matrix33 CreateRotation(Vec3 rotation)
        {
            return CreateRotation(rotation.X, rotation.Y, rotation.Z);
        }

        public static Matrix33 CreateRotation(double radiansX, double radiansY, double radiansZ)
        {
            return CreateRotationZ(radiansZ) * CreateRotationY(radiansY) * CreateRotationX(radiansX);
        }

        public static Matrix33 CreateRotation(double radians)
        {
            return CreateRotationZ(radians);
        }

        public static Matrix33 CreateTranslation(Vec2 v)
        {
            return CreateTranslation(v.X, v.Y);
        }

        public static Matrix33 CreateTranslation(double tx, double ty)
        {
            return new Matrix33(
                1.0, 0.0, tx,
                0.0, 1.0, ty,
                0.0, 0.0, 1.0);
        }

        public static Matrix33 CreateScale(Vec2 s)
        {
            return CreateScale(s.X, s.Y, 1.0);
        }

        public static Matrix33 CreateScale(Vec3 s)
        {
            return CreateScale(s.X, s.Y, s.Z);
        }

        public static Matrix33 CreateScale(double sx, double sy, double sz)
        {
            return new Matrix33(
                sx, 0.0, 0.0,
                0.0, sy, 0.0,
                0.0, 0.0, sz);
        }

        public static Matrix33 CreateScale(double s)
        {
            return CreateScale(s, s, s);
        }

        public static Matrix33 CreateRotationScale(Vec3 rotation, Vec3 scale)
        {
            return CreateScale(scale) * CreateRotation(rotation);
        }

    }
}
